<?php

1->架构思考
   . 目前微服务架构是否能够扛得住'海量并发', 如果扛不住的话, 那么该使用怎样的方法'限制流量请求'?
   . '降级/熔断'什么时候使用?
   . 我们的服务是否真的需要使用?


2->服务异常引发的问题
   -) '服务雪崩'
       在微服务集群架构模式下, 当有一个小服务的挂掉, 蔓延到'其他调用该服务的服务', 最终导致所有请求超时!
       (上游服务问题, 引发一系列下游服务响应超时, 从而'拖垮整个系统'!)

   -) '解决方案'
       . 降低异常服务的串联影响 (隔离异常服务)
       . 减压快速失败 (熔断)
       . 降级 (保证最小可用性 - 活着就行)


3->容错救星————Hystrix
   -) '优点'
       . 防止服务雪崩
       . 隔离异常服务
       . 提供服务降级方案

   -) '引子'
      《断舍离》，是日本作家山下英子的著作，这本书传达了一种生活理念。断=>不买、不收取不需要的东西。舍=>处理掉堆放在家里没用的东西。离=>舍弃对物质的迷恋，
       让自己处于宽敞舒适，自由自在的空间。

       对过往不迷恋，拿得起放得下，这样的生活哲学确实可以帮助人们度过一些困难时期。我们知道Hystrix也是为了帮服务节点度过他们的困难时期（缓解异常、雪崩带来的影响），
       它也有同样一套佛系的设计理念，分别对应Hystrix中三个特色功能
       . 断 - 熔断
       . 舍 - 降级
       . 离 - 隔离

   -) '服务降级'
       # 无端拜失仪，放弃令自新
       #                   - 宋·苏轼 《古风·此秦观诗》
       微服务架构强调高可用，但并非高一致性，在一致性方面远比不上银行的大型机系统。也就是说，在日常服务调用阶段会出现一系列的调用异常，
       最常见的就是服务下线。举个例子：重启服务节点的时候，服务下线指令发送到注册中心后，这时还没来得及通过服务发现机制同步到客户端，
       因此某些服务调用请求依然会发送到这台机器，但由于服务已经下线，最终调用方只能无功而返: 404 Not Found。

       再举一个破坏力更大的例子。前面我们讲到了服务的雪崩效应，大家可能只听说过缓存雪崩，其实雪崩效应不仅仅针对缓存，它更大的危害是在大规模分布式应用上。
       我们举一个真实的案例，电商系统很多模块都依赖营销优惠服务，比如商品详情页、搜索列表页、购物车页和下单页面都依赖营销服务来计算优惠价格，
       因此这个服务承载的负载压力可谓非常之高（这块服务的承压可以排在淘系全链路top5）。
       我们设想，假如这个服务出现了异常，导致响应超时，那么所有依赖它的下游系统的响应时间都会被拉长，这就引发了一个滚雪球的雪崩效应，
       由最上游的系统问题，引发了一系列下游系统响应超时，最终导致整个系统被拖垮。
                                        +---------+
                                        | 方法调用 |
                                        +---------+
                                             |
                                     +--------------+
                                     | Hystrix 代理  | ----------------> 降级逻辑
                                     +--------------+
                                             X (代表服务不可用)
                                 +----------------------+
                                 |        目标服务       |
                                 | Timeout   Exception  |
                                 +----------------------+
       服务降级用来应对上面的几种情况再合适不过了，假如HystrixClient调用目标请求的时候发生异常（exception），这时Hystrix会自动把这个请求转发到降级逻辑中，
       由服务调用方来编写异常处理逻辑。对响应超时的场景来说，我们可以通过配置Hystrix的超时等待时间（和Ribbon的timeout是两个不同配置），
       把超时响应的服务调用也当做是异常情况，转发到'fallback'逻辑中进行处理。

   -) '服务熔断'
       # 天门中断楚江开，碧水东流至此回
			 #        									- 唐·李白 《望天门山》
       服务熔断是建立在服务降级之上的一个异常处理措施，你可以将它看做是服务降级的升级版。服务降级需要等待HTTP请求从服务节点返回异常或超时，
       再转向fallback逻辑，但是服务熔断引入了一种叫'断路器/熔断器'的机制，当断路器打开的时候，对服务的调用请求不会发送到目标服务节点，直接转向fallback逻辑。
                                       +---------+
                                       | 方法调用 |
                                       +---------+
                                            |
                                    +--------------+
                                       Hystrix 代理
                                    +--------------+
                                            |
                        +-------------------+-------------------+
                        |                   |                   |
                     ServiceA            ServiceB             断路器   --------> fallback
                                                                X (代表服务不可用)
                                                             ServiceC
        断路器的['打开/关闭']有很多的判断标准 (例如: 每10个请求，失败数量达到8个的时候打开断路器）.
        服务一时失败，不代表一直失败，Hystrix也有一些配置规则，会主动去判断断路器关闭的时机。
        断路器可以显著缓解由QPS (Query Per Second，每秒访问请求，用来衡量系统当前压力)激增导致的雪崩效应，由于断路器打开后，
        请求直接转向 [fallback] 而不会发起服务调用，因此会大幅降低承压服务的系统压力。

    -) '线程隔离'
        # 明日隔山岳，世事两茫茫
    		#										- 唐·杜甫 《赠卫八处士》
        大家知道Web容器通常有一个线程池来接待来访请求，如果并发量过高，线程池被打满了就会影响后面请求的响应。在我们应用内部，
        假如我们提供了3个微服务，分别是A，B，C。如果请求A服务的调用量过多，我们会发现所有可用线程都会逐渐被Service A占用，
        接下来的现象就是服务B和服务C没有系统资源可供调用.
                                       +---------+
                                       | 方法调用 |
                                       +---------+
                                            |
                                    +--------------+
                                    | Hystrix 代理  |
                                    +--------------+
                                            |
                        +-------------------+-------------------+
                        |                   |                   |
                      10线程 X(服务不可用)  20线程               10线程
                     ServiceA            ServiceB            ServiceC

        Hystrix通过线程隔离的方案，将执行服务调用的代码与容器本身的线程池（比如tomcat thread pool）进行隔离，
        我们可以配置每个服务所需线程的最大数量，这样一来，即便一个服务的线程池被吃满，也不会影响其他服务。



4->降级盯梢注解————HystrixCommand
   -) '单独使用'
       一旦被该注解打上, 那么就会'紧盯该方法', 一旦该方法'顶不住或者搞幺蛾子' (异常情况), 那么就会被'劳改'.
       @HystrixCommand(fallbackMethod = "putInPrison")
       public String happendError() {
            throw RuntimeException("Fuck! The Error is comming agen!!!");
       }
       注意: 此处指定的'降级方法', 需要和原始方法有一致的方法签名 (参数类型, 参数数量, 参数顺序),
            也就是说，如果原方法声明了一个参数String，那么降级方法也要声明同样的参数，Hystrix会'原封不动'的把当前参数传递给降级方法。

   -) '搭配Feign'
       当Hystrix和Feign共同使用的时候，还有一种配置方式，那就是在FeignClient注解中指定一个class，
       在这个class里可以处理'Feign接口中声明的所有方法'的降级需求。
       @FeignClient(name = "eureka-client", fallback = Fallback.class)
       public interface IMyHelloService {
       }

   -) '异常捕获流程'
                                      @HystrixCommand
                                             |
                                       Aspect切面拦截
                                             |
                                       RequestCache ------(开启)------> 返回Cache
                                             |
                                           (关闭)
                                             |
                                        注册Observer
                                             |
                                          发起调用   ------> 异常抛出 ------> 回调fallback

       @HystrixCommand: 安插在方法上的锦衣卫, 标识此方法由Hystrix监管.

       AspectJ:
       运用Spring的切面能力，给带有@HystrixCommand注解的方法配置了切面点，在方法调用的时候，将首先执行切面逻辑。

       RequestCache:
       . 如果处于'开启状态'，则尝试用CacheKey从本地缓存中获取数据，也就不用发起方法调用了
       . 如果处于'关闭状态'，就继续往下走到最烧脑的部分，Observer

       注册Observer:
       Observer是观察者模式（在RxJava中又叫Observable），但这里只是一个幌子，这个Observer背后运用RxJava注册了一堆异步回调函数，
       当方法正常执行、异常抛出、结束或其他状态的时候，将会触发对应的回调函数进行处理，而且回调函数里面还会嵌套回调函数。
      （Hystrix开发团队是不是有个很怕失业的程序员？写这么复杂是要别人没法接手吗？）

       发起调用:
       在发起调用之前，将会'检查熔断状态'，如果断路器当前处于开启的状态，那么将直接走向fallback流程。如果断路器处于关闭，则发起真正的调用

       异常:
       前面你来我往这么久，就是等方法调用抛异常。异常触发了步骤4中注册的回调函数，然后直接转给了降级方法.


5->服务降级常用方案
   话说进了fallback的多多少少都是犯了点错的人，犯了错就要老实交代，我们六扇门的锦衣卫也不是吃素的，对付进了fallback的人，那手段可多了去了，就看你招还是不招？
   -) '静默处理 - 视而不见'
       所谓的静默处理, 就是什么也不干, 在fallback逻辑中直接返回一个空值Null.
       思考: 那我用 [try-catch] 捕捉异常不也是能达到一样的效果吗？
       原因: 其实不然，首先try-catch只能处理异常抛出的情况，并不能做超时判定。
            其次，使用try-catch就要在代码里包含异常处理块，我们在程序设计时讲究 [单一职责] 和 [开闭原则]。正所谓凯撒的归凯撒，上帝的归上帝，
            既然有了专门的fallback处理类，那这个工作还是交给fallback来吧，这样你的业务代码也落个清爽。

   -) '默认值 - 组织包分配'
       在并不确定真实结果的情况下返回一个默认值.
       打个比方，假如在商品详情页调用营销优惠接口时发生了故障，无法返回正确的计算结果，这里我们就可以在fallback逻辑中返回商品原价，
       作为打折后的价格，这样就相当于返回了一个没有打折优惠的计算结果。

       这种方式下接口的返回值并不是真实的，因此不能应用在某些核心主链路中。举个例子，比如下单页面就是核心主链路，是最终确定订单价格的关键步骤。
       假如我们对订单优惠计算采用了这种瞒天过海的默认值，那么就会实际造成用户损失。因此，这里面的优惠计算决不能返回默认值，一定要得出真实结果，
       如果无法获取那么宁可返回异常中断下单操作。

       那为什么商品详情页可以用默认值降级，而下单页面不能呢？这就要讲到主链路的规划，
       简单来说，电商平台的用户购物行为是一个漏斗模型，上宽下窄，用户流量在漏斗口最多，在尾部最少，越接近尾部的流量被转化为购物行为的比例就越高，
       因此越到后面对降级的容忍度就越低。商品搜索和商品详情页处于漏斗的上部，主要是导流端，在没有发生金钱往来的情况下我们可以容忍一定程度的降级误差。
       但对于下单页，这是整个漏斗模型的尾部，直接发生交易的环节，绝不能容忍任何金钱上的误差。老师在实际工作里设计商品详情页服务的时候，
       规定了淘系营销服务接口响应时间的上限是1000ms，超过这个数字则自动降级为0优惠.

   -) '好好改造 - 想办法恢复服务'
       这个才能称得上是正儿八经的积极措施，fallback会尝试用各种方法获取正确的返回值，有这么几个常用场景。
       缓存异常: 假如因为缓存故障无法获取数据，在fallback逻辑中可以转而访问底层数据库（这个方法不能用在热点数据上，否则可能把数据库打挂，
               或者引发更大范围的服务降级和熔断，要谨慎使用!!!）。反过来如果数据库发生故障，也可以在fallback里访问缓存，但要注意数据一致性.

       重试: Ribbon可以处理超时重试，但对于异常情况来说 (比如当前资源被暂时锁定)，我们可以在fallback中自己尝试'重新发起接口'调用.

       切换备库: 一般大型应用都会采用 [主从+备库] 的方式做灾备，假如我们的主从库都发生了故障，往往需要人工将数据源切换到备份数据库（参考支付宝2015年的挖掘机事故）,
               我们在fallback中可以先于人工干预之前自动访问备库数据。这个场景尽量限定在核心主链路接口上，不要动不动就去访问备库，以免造成脏读幻读.

       人工干预: 有些'极其重要的接口'，对异常不能容忍，这里可以借助fallback启动人工干预流程，比如做日志打点，通过监控组件触发报警，通知人工介入!

   -) '一错再错 - 多次降级'
       总有那么几个顽固分子，在fallback里不好好改造，又捣鼓出一个异常来。这时候我们可以做二次降级，也就是在fallback中再引入一个fallback。
       当然，你也可以引入三四五六七八更多层的降级，对应一些复杂的大型应用，比如淘系很多核心系统，多级降级是很常见的，
       根据系统故障的严重程度采取'更精细粒度'的降级方案。
       那假如这一连串降级全部都失败了，难道要牢底坐穿不成？对这种一错再错无药可救的顽固分子，锦衣卫也是没有办法啊，那只好放你走了，'将异常抛到最外层'!


6->使用
   -) 'pom'
       <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
       </dependency>

   -) '注解'
       @SpringBootApplication
       @EnableDiscoveryClient
       @EnableFeignClients
       @EnableCircuitBreaker

   -) '配置文件'
       # 开启Feign下面的Hystrix功能
       feign:
         hystrix:
           enabled: true
       # 是否开启[服务降级]
       hystrix:
         command:
           default:
             fallback:
               enable: true

   -) '类'
       @FeignClient(value = "eureka-client", fallback = SayHystrix.class)
       public interface IHystrixService {}

       @Component
       public class SayHystrix implements IHystrixService {}
       注意: fallback指定的类, '一定要继承该接口'! 此种方法将所有接口中的方法'都做了降级措施'.


7->超时配置
   -) '全局超时'
       . 超时时间
       . 超时后终止线程
       . 取消的时候终止线程
       feign:
         hystrix:
           enabled: true       # 开启Feign的Hystrix
       hystrix:
         command:
           default:
#          MyService#retry(int)    此处为[单方法配置] fallback的类名#方法(参数), 单方法的配置会[覆盖全局配置]
             fallback:
               enable: true        # 开启服务降级
             execution:
               timeout:
                 enable: true      # 开启超时
               isolation:
                 thread:
                   timeoutInMilliseconds: 3000     # 超时时间
                   interrupOnTimeout: true         # 超时以后终止线程
                   interrupOnFutureCancel: true    # 取消的时候终止线程

    -) '单方法-main生成'
        System.out.println(Feign.configKey(SayHystrix.class, SayHystrix.class.getMethod("error", String.class)));

    -) '单方法简单配置-搭配下面'
        @CacheResult
        # 如果没有配置的话, 那么默认为[方法名], 如果存在[方法重载]的话, 那么就使用[方法签名](好麻烦!),
        # 所以需要的话, 最好还是我们默认给个名字!
        @HystrixCommand(commandKey = "cacheKey")
        public String requestCache(@CacheKey String name) {
        }
        hystrix:
          command:
            cacheKey:    # 此处指定的就是[方法]的[降级配置]


8->减压————RequestCache
   说明: RequestCache只是一种'减压手段'!
   -) '减压'
       @CacheKey注解打在'方法入参'上, 这样后续请求就会'根据key获取缓存中的值'!

       @CacheResult
       @HystrixCommand(commandKey = "cacheKey")
       public String requestCache(@CacheKey String name) {
           // 拿到[上下文]
           @Cleanup HystrixRequestContext context = HystrixRequestContext.initializeContext();
           String error = hystrixService.error(name);
           error = hystrixService.error(name);
           return error;
       }

   -) '开启缓存功能'
       hystrix:
        command:
          default:
            requestCahe:
              enable: true

   -) '1-2-1'
       1: 开启访问缓存配置
       2: @CacheResult + @CacheKey
       1: HystrixRequestContext



9->一错再错————多次降级
   说明: 针对'实现了需要降级的接口'内的方法如果依旧'异常', 那么可以在其方法上进一步'降级'
   -)'注解'
      @Override
      @HystrixCommand(fallbackMethod = "sayHelloAgain", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
      })
      public String sayHello() {
         return "I'm Ok ...";
      }

      @HystrixCommand(fallbackMethod = "sayHelloAgain2")
      public String sayHelloAgain() {
         return  "Sorry I'm Again ...";;
      }

      public String sayHelloAgain2() {
         return "Sorry I'm Again again ...";
      }
      注意:
      此fallbackMethod配置的方法, 只能存在'此类中'!
      如果'多次超时方法'配置的超时时间 > 全局配置的超时时间, 那么以'最小超时时间'为准!
      但是如果是'数据库挂了', 那么无论降级几次都顶不住, 这个时候就需要一次性解决 - '熔断'!



10->超时之争————Ribbon——————Hystrix
    前景: Ribbon 和 Hystrix 都是 Feign的两员爱将, 那么谁的配置才会最为服务的'最终配置'呢...
    -) 'Ribbon - 超时计算公式'
        最大超时时间 = (连接超时时间+接口超时时间) * (当前节点重试次数+1) * (换节点重试次数+1)

    -) 'Hystrix - 超时计算公式'
        hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000

    -) '口诀'
        Hystrix和Ribbon是一个共同作用的关系, '谁先到达超时指标'就会率先起作用.

        以上全局配置设置了Hystrix的熔断时间为1000ms。这里Hystrix的超时时间设置比Ribbon配置的时间短，那么不等Ribbon重试结束，
        Hystrix判定超时后就会直接执行熔断逻辑。

        通常来讲，Hystrix的熔断时间要比Ribbon的最长超时时间设置的略长一些，这样就可以让Ribbon的重试机制充分发挥作用，
        以免出现还没来得及重试就进入fallback逻辑的情况发生。

    -) 'Hystrix-方法级别超时控制'
        方法签名:
        hystrix.command.ClassName#methodName(Integer).execution.isolation.thread.timeoutInMilliseconds=1000
        生成: Feign.configKey(MyService.class, MyService.class.getMethod("findFriend", Integer.class))

        commandKey:
        @HystrixCommand(commandKey = "myKey"，fallbackMethod = "fallback")
        hystrix.command.myKey.execution.isolation.thread.timeoutInMilliseconds=1000
